// Emacs style mode select	 -*- C++ -*-
//-----------------------------------------------------------------------------
//
// $Id:$
//
// Copyright (C) 1993-1996 by id Software, Inc.
//
// This source is available for distribution and/or modification
// only under the terms of the DOOM Source Code License as
// published by id Software. All rights reserved.
//
// The source is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
// for more details.
//
// $Log:$
//
// DESCRIPTION:
//		Implements special effects:
//		Texture animation, height or lighting changes
//		 according to adjacent sectors, respective
//		 utility functions, etc.
//		Line Tag handling. Line and Sector triggers.
//
//-----------------------------------------------------------------------------

static const char
rcsid[] = "$Id: p_spec.c,v 1.6 1997/02/03 22:45:12 b1 Exp $";

#include <stdlib.h>
#include "doomdef.h"
#include "doomstat.h"

#include "i_system.h"
#include "z_zone.h"
#include "m_argv.h"
#include "m_random.h"
#include "w_wad.h"

#include "r_local.h"
#include "p_local.h"

#include "g_game.h"

#include "s_sound.h"

// State.
#include "r_state.h"

// Data.
#include "sounds.h"


//
// Animating textures and planes
// There is another anim_t used in wi_stuff, unrelated.
//
typedef struct
{
    boolean		istexture;
    int			picnum;
    int			basepic;
    int			numpics;
    int			speed;

} anim_t;

//
//		source animation definition
//
typedef struct
{
    boolean		istexture;		// if false, it is a flat
    char		endname[9];
    char		startname[9];
    int			speed;
} animdef_t;



#define MAXANIMS				32

extern anim_t	anims[MAXANIMS];
extern anim_t*	lastanim;

//
// P_InitPicAnims
//

// Floor/ceiling animation sequences,
//	defined by first and last frame,
//	i.e. the flat (64x64 tile) name to
//	be used.
// The full animation sequence is given
//	using all the flats between the start
//	and end entry, in the order found in
//	the WAD file.
//
animdef_t				animdefs[] =
{
    {false,		"NUKAGE3",		"NUKAGE1",		8},
    {false,		"FWATER4",		"FWATER1",		8},
    {false,		"SWATER4",		"SWATER1",		8},
    {false,		"LAVA4",		"LAVA1",		8},
    {false,		"BLOOD3",		"BLOOD1",		8},

    // DOOM II flat animations.
    {false,		"RROCK08",		"RROCK05",		8},
    {false,		"SLIME04",		"SLIME01",		8},
    {false,		"SLIME08",		"SLIME05",		8},
    {false,		"SLIME12",		"SLIME09",		8},

    {true,		"BLODGR4",		"BLODGR1",		8},
    {true,		"SLADRIP3",		"SLADRIP1",		8},

    {true,		"BLODRIP4",		"BLODRIP1",		8},
    {true,		"FIREWALL",		"FIREWALA",		8},
    {true,		"GSTFONT3",		"GSTFONT1",		8},
    {true,		"FIRELAVA",		"FIRELAV3",		8},
    {true,		"FIREMAG3",		"FIREMAG1",		8},
    {true,		"FIREBLU2",		"FIREBLU1",		8},
    {true,		"ROCKRED3",		"ROCKRED1",		8},

    {true,		"BFALL4",		"BFALL1",		8},
    {true,		"SFALL4",		"SFALL1",		8},
    {true,		"WFALL4",		"WFALL1",		8},
    {true,		"DBRAIN4",		"DBRAIN1",		8},

    {-1}
};

anim_t			anims[MAXANIMS];
anim_t*			lastanim;


//
//		Animating line specials
//
#define MAXLINEANIMS			64

extern	short	numlinespecials;
extern	line_t* linespeciallist[MAXLINEANIMS];



void P_InitPicAnims (void)
{
    int			i;


    //	Init animation
    lastanim = anims;
    for (i=0 ; animdefs[i].istexture != -1 ; i++)
    {
        if (animdefs[i].istexture)
        {
            // different episode ?
            if (R_CheckTextureNumForName(animdefs[i].startname) == -1)
                continue;

            lastanim->picnum = R_TextureNumForName (animdefs[i].endname);
            lastanim->basepic = R_TextureNumForName (animdefs[i].startname);
        }
        else
        {
            if (W_CheckNumForName(animdefs[i].startname) == -1)
                continue;

            lastanim->picnum = R_FlatNumForName (animdefs[i].endname);
            lastanim->basepic = R_FlatNumForName (animdefs[i].startname);
        }

        lastanim->istexture = animdefs[i].istexture;
        lastanim->numpics = lastanim->picnum - lastanim->basepic + 1;

        if (lastanim->numpics < 2)
            I_Error ("P_InitPicAnims: bad cycle from %s to %s",
                     animdefs[i].startname,
                     animdefs[i].endname);

        lastanim->speed = animdefs[i].speed;
        lastanim++;
    }

}



//
// UTILITIES
//



//
// getSide()
// Will return a side_t*
//	given the number of the current sector,
//	the line number, and the side (0/1) that you want.
//
side_t*
getSide
( int			currentSector,
  int			line,
  int			side )
{
    return &sides[ (sectors[currentSector].lines[line])->sidenum[side] ];
}


//
// getSector()
// Will return a sector_t*
//	given the number of the current sector,
//	the line number and the side (0/1) that you want.
//
sector_t*
getSector
( int			currentSector,
  int			line,
  int			side )
{
    return sides[ (sectors[currentSector].lines[line])->sidenum[side] ].sector;
}


//
// twoSided()
// Given the sector number and the line number,
//	it will tell you whether the line is two-sided or not.
//
int
twoSided
( int	sector,
  int	line )
{
    return (sectors[sector].lines[line])->flags & ML_TWOSIDED;
}




//
// getNextSector()
// Return sector_t * of sector next to current.
// NULL if not two-sided line
//
sector_t*
getNextSector
( line_t*		line,
  sector_t*		sec )
{
    if (!(line->flags & ML_TWOSIDED))
        return NULL;

    if (line->frontsector == sec)
        return line->backsector;

    return line->frontsector;
}



//
// P_FindLowestFloorSurrounding()
// FIND LOWEST FLOOR HEIGHT IN SURROUNDING SECTORS
//
fixed_t P_FindLowestFloorSurrounding(sector_t* sec)
{
    int					i;
    line_t*				check;
    sector_t*			other;
    fixed_t				floor = sec->floorheight;

    for (i=0 ; i < sec->linecount ; i++)
    {
        check = sec->lines[i];
        other = getNextSector(check,sec);

        if (!other)
            continue;

        if (other->floorheight < floor)
            floor = other->floorheight;
    }
    return floor;
}



//
// P_FindHighestFloorSurrounding()
// FIND HIGHEST FLOOR HEIGHT IN SURROUNDING SECTORS
//
fixed_t P_FindHighestFloorSurrounding(sector_t *sec)
{
    int					i;
    line_t*				check;
    sector_t*			other;
    fixed_t				floor = -500*FRACUNIT;

    for (i=0 ; i < sec->linecount ; i++)
    {
        check = sec->lines[i];
        other = getNextSector(check,sec);

        if (!other)
            continue;

        if (other->floorheight > floor)
            floor = other->floorheight;
    }
    return floor;
}



//
// P_FindNextHighestFloor
// FIND NEXT HIGHEST FLOOR IN SURROUNDING SECTORS
// Note: this should be doable w/o a fixed array.

// 20 adjoining sectors max!
#define MAX_ADJOINING_SECTORS			20

fixed_t
P_FindNextHighestFloor
( sector_t*		sec,
  int			currentheight )
{
    int					i;
    int					h;
    int					min;
    line_t*				check;
    sector_t*			other;
    fixed_t				height = currentheight;


    fixed_t				heightlist[MAX_ADJOINING_SECTORS];

    for (i=0, h=0 ; i < sec->linecount ; i++)
    {
        check = sec->lines[i];
        other = getNextSector(check,sec);

        if (!other)
            continue;

        if (other->floorheight > height)
            heightlist[h++] = other->floorheight;

        // Check for overflow. Exit.
        if ( h >= MAX_ADJOINING_SECTORS )
        {
            // fprintf( stderr, "Sector with more than 20 adjoining sectors\n" );
            break;
        }
    }

    // Find lowest height in list
    if (!h)
        return currentheight;

    min = heightlist[0];

    // Range checking?
    for (i = 1; i < h; i++)
        if (heightlist[i] < min)
            min = heightlist[i];

    return min;
}


//
// FIND LOWEST CEILING IN THE SURROUNDING SECTORS
//
fixed_t
P_FindLowestCeilingSurrounding(sector_t* sec)
{
    int					i;
    line_t*				check;
    sector_t*			other;
    fixed_t				height = MAXINT;

    for (i=0 ; i < sec->linecount ; i++)
    {
        check = sec->lines[i];
        other = getNextSector(check,sec);

        if (!other)
            continue;

        if (other->ceilingheight < height)
            height = other->ceilingheight;
    }
    return height;
}


//
// FIND HIGHEST CEILING IN THE SURROUNDING SECTORS
//
fixed_t P_FindHighestCeilingSurrounding(sector_t* sec)
{
    int			i;
    line_t*		check;
    sector_t*	other;
    fixed_t		height = 0;

    for (i=0 ; i < sec->linecount ; i++)
    {
        check = sec->lines[i];
        other = getNextSector(check,sec);

        if (!other)
            continue;

        if (other->ceilingheight > height)
            height = other->ceilingheight;
    }
    return height;
}



//
// RETURN NEXT SECTOR # THAT LINE TAG REFERS TO
//
int
P_FindSectorFromLineTag
( line_t*		line,
  int			start )
{
    int i;

    for (i=start+1; i<numsectors; i++)
        if (sectors[i].tag == line->tag)
            return i;

    return -1;
}




//
// Find minimum light from an adjacent sector
//
int
P_FindMinSurroundingLight
( sector_t*		sector,
  int			max )
{
    int			i;
    int			min;
    line_t*		line;
    sector_t*	check;

    min = max;
    for (i=0 ; i < sector->linecount ; i++)
    {
        line = sector->lines[i];
        check = getNextSector(line,sector);

        if (!check)
            continue;

        if (check->lightlevel < min)
            min = check->lightlevel;
    }
    return min;
}



//
// EVENTS
// Events are operations triggered by using, crossing,
// or shooting special lines, or by timed thinkers.
//

//
// P_CrossSpecialLine - TRIGGER
// Called every time a thing origin is about
//	to cross a line with a non 0 special.
//
void
P_CrossSpecialLine
( int			linenum,
  int			side,
  mobj_t*		thing )
{
    line_t*		line;
    int			ok;

    line = &lines[linenum];

    //	Triggers that other things can activate
    if (!thing->player)
    {
        // Things that should NOT trigger specials...
        switch(thing->type)
        {
            case MT_ROCKET:
            case MT_PLASMA:
            case MT_BFG:
            case MT_TROOPSHOT:
            case MT_HEADSHOT:
            case MT_BRUISERSHOT:
                return;
                break;

            default:
                break;
        }

        ok = 0;
        switch(line->special)
        {
            case 39:		// TELEPORT TRIGGER
            case 97:		// TELEPORT RETRIGGER
            case 125:		// TELEPORT MONSTERONLY TRIGGER
            case 126:		// TELEPORT MONSTERONLY RETRIGGER
            case 4:		// RAISE DOOR
            case 10:		// PLAT DOWN-WAIT-UP-STAY TRIGGER
            case 88:		// PLAT DOWN-WAIT-UP-STAY RETRIGGER
                ok = 1;
                break;
        }
        if (!ok)
            return;
    }


    // Note: could use some const's here.
    switch (line->special)
    {
        // TRIGGERS.
        // All from here to RETRIGGERS.
        case 2:
            // Open Door
            EV_DoDoor(line,open);
            line->special = 0;
            break;

        case 3:
            // Close Door
            EV_DoDoor(line,close);
            line->special = 0;
            break;

        case 4:
            // Raise Door
            EV_DoDoor(line,normal);
            line->special = 0;
            break;

        case 5:
            // Raise Floor
            EV_DoFloor(line,raiseFloor);
            line->special = 0;
            break;

        case 6:
            // Fast Ceiling Crush & Raise
            EV_DoCeiling(line,fastCrushAndRaise);
            line->special = 0;
            break;

        case 8:
            // Build Stairs
            EV_BuildStairs(line,build8);
            line->special = 0;
            break;

        case 10:
            // PlatDownWaitUp
            EV_DoPlat(line,downWaitUpStay,0);
            line->special = 0;
            break;

        case 12:
            // Light Turn On - brightest near
            EV_LightTurnOn(line,0);
            line->special = 0;
            break;

        case 13:
            // Light Turn On 255
            EV_LightTurnOn(line,255);
            line->special = 0;
            break;

        case 16:
            // Close Door 30
            EV_DoDoor(line,close30ThenOpen);
            line->special = 0;
            break;

        case 17:
            // Start Light Strobing
            EV_StartLightStrobing(line);
            line->special = 0;
            break;

        case 19:
            // Lower Floor
            EV_DoFloor(line,lowerFloor);
            line->special = 0;
            break;

        case 22:
            // Raise floor to nearest height and change texture
            EV_DoPlat(line,raiseToNearestAndChange,0);
            line->special = 0;
            break;

        case 25:
            // Ceiling Crush and Raise
            EV_DoCeiling(line,crushAndRaise);
            line->special = 0;
            break;

        case 30:
            // Raise floor to shortest texture height
            //	on either side of lines.
            EV_DoFloor(line,raiseToTexture);
            line->special = 0;
            break;

        case 35:
            // Lights Very Dark
            EV_LightTurnOn(line,35);
            line->special = 0;
            break;

        case 36:
            // Lower Floor (TURBO)
            EV_DoFloor(line,turboLower);
            line->special = 0;
            break;

        case 37:
            // LowerAndChange
            EV_DoFloor(line,lowerAndChange);
            line->special = 0;
            break;

        case 38:
            // Lower Floor To Lowest
            EV_DoFloor( line, lowerFloorToLowest );
            line->special = 0;
            break;

        case 39:
            // TELEPORT!
            EV_Teleport( line, side, thing );
            line->special = 0;
            break;

        case 40:
            // RaiseCeilingLowerFloor
            EV_DoCeiling( line, raiseToHighest );
            EV_DoFloor( line, lowerFloorToLowest );
            line->special = 0;
            break;

        case 44:
            // Ceiling Crush
            EV_DoCeiling( line, lowerAndCrush );
            line->special = 0;
            break;

        case 52:
            // EXIT!
            G_ExitLevel ();
            break;

        case 53:
            // Perpetual Platform Raise
            EV_DoPlat(line,perpetualRaise,0);
            line->special = 0;
            break;

        case 54:
            // Platform Stop
            EV_StopPlat(line);
            line->special = 0;
            break;

        case 56:
            // Raise Floor Crush
            EV_DoFloor(line,raiseFloorCrush);
            line->special = 0;
            break;

        case 57:
            // Ceiling Crush Stop
            EV_CeilingCrushStop(line);
            line->special = 0;
            break;

        case 58:
            // Raise Floor 24
            EV_DoFloor(line,raiseFloor24);
            line->special = 0;
            break;

        case 59:
            // Raise Floor 24 And Change
            EV_DoFloor(line,raiseFloor24AndChange);
            line->special = 0;
            break;

        case 104:
            // Turn lights off in sector(tag)
            EV_TurnTagLightsOff(line);
            line->special = 0;
            break;

        case 108:
            // Blazing Door Raise (faster than TURBO!)
            EV_DoDoor (line,blazeRaise);
            line->special = 0;
            break;

        case 109:
            // Blazing Door Open (faster than TURBO!)
            EV_DoDoor (line,blazeOpen);
            line->special = 0;
            break;

        case 100:
            // Build Stairs Turbo 16
            EV_BuildStairs(line,turbo16);
            line->special = 0;
            break;

        case 110:
            // Blazing Door Close (faster than TURBO!)
            EV_DoDoor (line,blazeClose);
            line->special = 0;
            break;

        case 119:
            // Raise floor to nearest surr. floor
            EV_DoFloor(line,raiseFloorToNearest);
            line->special = 0;
            break;

        case 121:
            // Blazing PlatDownWaitUpStay
            EV_DoPlat(line,blazeDWUS,0);
            line->special = 0;
            break;

        case 124:
            // Secret EXIT
            G_SecretExitLevel ();
            break;

        case 125:
            // TELEPORT MonsterONLY
            if (!thing->player)
            {
                EV_Teleport( line, side, thing );
                line->special = 0;
            }
            break;

        case 130:
            // Raise Floor Turbo
            EV_DoFloor(line,raiseFloorTurbo);
            line->special = 0;
            break;

        case 141:
            // Silent Ceiling Crush & Raise
            EV_DoCeiling(line,silentCrushAndRaise);
            line->special = 0;
            break;

        // RETRIGGERS.	All from here till end.
        case 72:
            // Ceiling Crush
            EV_DoCeiling( line, lowerAndCrush );
            break;

        case 73:
            // Ceiling Crush and Raise
            EV_DoCeiling(line,crushAndRaise);
            break;

        case 74:
            // Ceiling Crush Stop
            EV_CeilingCrushStop(line);
            break;

        case 75:
            // Close Door
            EV_DoDoor(line,close);
            break;

        case 76:
            // Close Door 30
            EV_DoDoor(line,close30ThenOpen);
            break;

        case 77:
            // Fast Ceiling Crush & Raise
            EV_DoCeiling(line,fastCrushAndRaise);
            break;

        case 79:
            // Lights Very Dark
            EV_LightTurnOn(line,35);
            break;

        case 80:
            // Light Turn On - brightest near
            EV_LightTurnOn(line,0);
            break;

        case 81:
            // Light Turn On 255
            EV_LightTurnOn(line,255);
            break;

        case 82:
            // Lower Floor To Lowest
            EV_DoFloor( line, lowerFloorToLowest );
            break;

        case 83:
            // Lower Floor
            EV_DoFloor(line,lowerFloor);
            break;

        case 84:
            // LowerAndChange
            EV_DoFloor(line,lowerAndChange);
            break;

        case 86:
            // Open Door
            EV_DoDoor(line,open);
            break;

        case 87:
            // Perpetual Platform Raise
            EV_DoPlat(line,perpetualRaise,0);
            break;

        case 88:
            // PlatDownWaitUp
            EV_DoPlat(line,downWaitUpStay,0);
            break;

        case 89:
            // Platform Stop
            EV_StopPlat(line);
            break;

        case 90:
            // Raise Door
            EV_DoDoor(line,normal);
            break;

        case 91:
            // Raise Floor
            EV_DoFloor(line,raiseFloor);
            break;

        case 92:
            // Raise Floor 24
            EV_DoFloor(line,raiseFloor24);
            break;

        case 93:
            // Raise Floor 24 And Change
            EV_DoFloor(line,raiseFloor24AndChange);
            break;

        case 94:
            // Raise Floor Crush
            EV_DoFloor(line,raiseFloorCrush);
            break;

        case 95:
            // Raise floor to nearest height
            // and change texture.
            EV_DoPlat(line,raiseToNearestAndChange,0);
            break;

        case 96:
            // Raise floor to shortest texture height
            // on either side of lines.
            EV_DoFloor(line,raiseToTexture);
            break;

        case 97:
            // TELEPORT!
            EV_Teleport( line, side, thing );
            break;

        case 98:
            // Lower Floor (TURBO)
            EV_DoFloor(line,turboLower);
            break;

        case 105:
            // Blazing Door Raise (faster than TURBO!)
            EV_DoDoor (line,blazeRaise);
            break;

        case 106:
            // Blazing Door Open (faster than TURBO!)
            EV_DoDoor (line,blazeOpen);
            break;

        case 107:
            // Blazing Door Close (faster than TURBO!)
            EV_DoDoor (line,blazeClose);
            break;

        case 120:
            // Blazing PlatDownWaitUpStay.
            EV_DoPlat(line,blazeDWUS,0);
            break;

        case 126:
            // TELEPORT MonsterONLY.
            if (!thing->player)
                EV_Teleport( line, side, thing );
            break;

        case 128:
            // Raise To Nearest Floor
            EV_DoFloor(line,raiseFloorToNearest);
            break;

        case 129:
            // Raise Floor Turbo
            EV_DoFloor(line,raiseFloorTurbo);
            break;
    }
}



//
// P_ShootSpecialLine - IMPACT SPECIALS
// Called when a thing shoots a special line.
//
void
P_ShootSpecialLine
( mobj_t*		thing,
  line_t*		line )
{
    int			ok;

    //	Impacts that other things can activate.
    if (!thing->player)
    {
        ok = 0;
        switch(line->special)
        {
            case 46:
                // OPEN DOOR IMPACT
                ok = 1;
                break;
        }
        if (!ok)
            return;
    }

    switch(line->special)
    {
        case 24:
            // RAISE FLOOR
            EV_DoFloor(line,raiseFloor);
            P_ChangeSwitchTexture(line,0);
            break;

        case 46:
            // OPEN DOOR
            EV_DoDoor(line,open);
            P_ChangeSwitchTexture(line,1);
            break;

        case 47:
            // RAISE FLOOR NEAR AND CHANGE
            EV_DoPlat(line,raiseToNearestAndChange,0);
            P_ChangeSwitchTexture(line,0);
            break;
    }
}



//
// P_PlayerInSpecialSector
// Called every tic frame
//	that the player origin is in a special sector
//
void P_PlayerInSpecialSector (player_t* player)
{
    sector_t*	sector;

    sector = player->mo->subsector->sector;

    // Falling, not all the way down yet?
    if (player->mo->z != sector->floorheight)
        return;

    // Has hitten ground.
    switch (sector->special)
    {
        case 5:
            // HELLSLIME DAMAGE
            if (!player->powers[pw_ironfeet])
                if (!(leveltime&0x1f))
                    P_DamageMobj (player->mo, NULL, NULL, 10);
            break;

        case 7:
            // NUKAGE DAMAGE
            if (!player->powers[pw_ironfeet])
                if (!(leveltime&0x1f))
                    P_DamageMobj (player->mo, NULL, NULL, 5);
            break;

        case 16:
        // SUPER HELLSLIME DAMAGE
        case 4:
            // STROBE HURT
            if (!player->powers[pw_ironfeet]
                    || (P_Random()<5) )
            {
                if (!(leveltime&0x1f))
                    P_DamageMobj (player->mo, NULL, NULL, 20);
            }
            break;

        case 9:
            // SECRET SECTOR
            player->secretcount++;
            sector->special = 0;
            break;

        case 11:
            // EXIT SUPER DAMAGE! (for E1M8 finale)
            player->cheats &= ~CF_GODMODE;

            if (!(leveltime&0x1f))
                P_DamageMobj (player->mo, NULL, NULL, 20);

            if (player->health <= 10)
                G_ExitLevel();
            break;

        default:
            I_Error ("P_PlayerInSpecialSector: "
                     "unknown special %i",
                     sector->special);
            break;
    };
}




//
// P_UpdateSpecials
// Animate planes, scroll walls, etc.
//
boolean			levelTimer;
int				levelTimeCount;

void P_UpdateSpecials (void)
{
    anim_t*		anim;
    int			pic;
    int			i;
    line_t*		line;


    //	LEVEL TIMER
    if (levelTimer == true)
    {
        levelTimeCount--;
        if (!levelTimeCount)
            G_ExitLevel();
    }

    //	ANIMATE FLATS AND TEXTURES GLOBALLY
    for (anim = anims ; anim < lastanim ; anim++)
    {
        for (i=anim->basepic ; i<anim->basepic+anim->numpics ; i++)
        {
            pic = anim->basepic + ( (leveltime/anim->speed + i)%anim->numpics );
            if (anim->istexture)
                texturetranslation[i] = pic;
            else
                flattranslation[i] = pic;
        }
    }


    //	ANIMATE LINE SPECIALS
    for (i = 0; i < numlinespecials; i++)
    {
        line = linespeciallist[i];
        switch(line->special)
        {
            case 48:
                // EFFECT FIRSTCOL SCROLL +
                sides[line->sidenum[0]].textureoffset += FRACUNIT;
                break;
        }
    }


    //	DO BUTTONS
    for (i = 0; i < MAXBUTTONS; i++)
        if (buttonlist[i].btimer)
        {
            buttonlist[i].btimer--;
            if (!buttonlist[i].btimer)
            {
                switch(buttonlist[i].where)
                {
                    case top:
                        sides[buttonlist[i].line->sidenum[0]].toptexture =
                            buttonlist[i].btexture;
                        break;

                    case middle:
                        sides[buttonlist[i].line->sidenum[0]].midtexture =
                            buttonlist[i].btexture;
                        break;

                    case bottom:
                        sides[buttonlist[i].line->sidenum[0]].bottomtexture =
                            buttonlist[i].btexture;
                        break;
                }
                S_StartSound((mobj_t *)&buttonlist[i].soundorg,sfx_swtchn);
                memset(&buttonlist[i],0,sizeof(button_t));
            }
        }

}



//
// Special Stuff that can not be categorized
//
int EV_DoDonut(line_t*	line)
{
    sector_t*			s1;
    sector_t*			s2;
    sector_t*			s3;
    int					secnum;
    int					rtn;
    int					i;
    floormove_t*		floor;

    secnum = -1;
    rtn = 0;
    while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0)
    {
        s1 = &sectors[secnum];

        // ALREADY MOVING?	IF SO, KEEP GOING...
        if (s1->specialdata)
            continue;

        rtn = 1;
        s2 = getNextSector(s1->lines[0],s1);
        for (i = 0; i < s2->linecount; i++)
        {
            if ((!s2->lines[i]->flags & ML_TWOSIDED) ||
                    (s2->lines[i]->backsector == s1))
                continue;
            s3 = s2->lines[i]->backsector;

            //	Spawn rising slime
            floor = Z_Malloc (sizeof(*floor), PU_LEVSPEC, 0);
            P_AddThinker (&floor->thinker);
            s2->specialdata = floor;
            floor->thinker.function.acp1 = (actionf_p1) T_MoveFloor;
            floor->type = donutRaise;
            floor->crush = false;
            floor->direction = 1;
            floor->sector = s2;
            floor->speed = FLOORSPEED / 2;
            floor->texture = s3->floorpic;
            floor->newspecial = 0;
            floor->floordestheight = s3->floorheight;

            //	Spawn lowering donut-hole
            floor = Z_Malloc (sizeof(*floor), PU_LEVSPEC, 0);
            P_AddThinker (&floor->thinker);
            s1->specialdata = floor;
            floor->thinker.function.acp1 = (actionf_p1) T_MoveFloor;
            floor->type = lowerFloor;
            floor->crush = false;
            floor->direction = -1;
            floor->sector = s1;
            floor->speed = FLOORSPEED / 2;
            floor->floordestheight = s3->floorheight;
            break;
        }
    }
    return rtn;
}



//
// SPECIAL SPAWNING
//

//
// P_SpawnSpecials
// After the map has been loaded, scan for specials
//	that spawn thinkers
//
short			numlinespecials;
line_t*			linespeciallist[MAXLINEANIMS];


// Parses command line parameters.
void P_SpawnSpecials (void)
{
    sector_t*	sector;
    int			i;
    int			episode;

    episode = 1;
    if (W_CheckNumForName("texture2") >= 0)
        episode = 2;


    // See if -TIMER needs to be used.
    levelTimer = false;

    i = M_CheckParm("-avg");
    if (i && deathmatch)
    {
        levelTimer = true;
        levelTimeCount = 20 * 60 * 35;
    }

    i = M_CheckParm("-timer");
    if (i && deathmatch)
    {
        int		time;
        time = atoi(myargv[i+1]) * 60 * 35;
        levelTimer = true;
        levelTimeCount = time;
    }

    //	Init special SECTORs.
    sector = sectors;
    for (i=0 ; i<numsectors ; i++, sector++)
    {
        if (!sector->special)
            continue;

        switch (sector->special)
        {
            case 1:
                // FLICKERING LIGHTS
                P_SpawnLightFlash (sector);
                break;

            case 2:
                // STROBE FAST
                P_SpawnStrobeFlash(sector,FASTDARK,0);
                break;

            case 3:
                // STROBE SLOW
                P_SpawnStrobeFlash(sector,SLOWDARK,0);
                break;

            case 4:
                // STROBE FAST/DEATH SLIME
                P_SpawnStrobeFlash(sector,FASTDARK,0);
                sector->special = 4;
                break;

            case 8:
                // GLOWING LIGHT
                P_SpawnGlowingLight(sector);
                break;
            case 9:
                // SECRET SECTOR
                totalsecret++;
                break;

            case 10:
                // DOOR CLOSE IN 30 SECONDS
                P_SpawnDoorCloseIn30 (sector);
                break;

            case 12:
                // SYNC STROBE SLOW
                P_SpawnStrobeFlash (sector, SLOWDARK, 1);
                break;

            case 13:
                // SYNC STROBE FAST
                P_SpawnStrobeFlash (sector, FASTDARK, 1);
                break;

            case 14:
                // DOOR RAISE IN 5 MINUTES
                P_SpawnDoorRaiseIn5Mins (sector, i);
                break;

            case 17:
                P_SpawnFireFlicker(sector);
                break;
        }
    }


    //	Init line EFFECTs
    numlinespecials = 0;
    for (i = 0; i < numlines; i++)
    {
        switch(lines[i].special)
        {
            case 48:
                // EFFECT FIRSTCOL SCROLL+
                linespeciallist[numlinespecials] = &lines[i];
                numlinespecials++;
                break;
        }
    }


    //	Init other misc stuff
    for (i = 0; i < MAXCEILINGS; i++)
        activeceilings[i] = NULL;

    for (i = 0; i < MAXPLATS; i++)
        activeplats[i] = NULL;

    for (i = 0; i < MAXBUTTONS; i++)
        memset(&buttonlist[i],0,sizeof(button_t));

    // UNUSED: no horizonal sliders.
    //	P_InitSlidingDoorFrames();
}
